package yycore

import chisel3._
import chisel3.util._
import chisel3.util.experimental.BoringUtils
import utils._
import common.Constants._


class IBufPtr extends CircularQueuePtr[IBufPtr](IBufSize) {
    //override def cloneType: IBufPtr.this.type = super.cloneType
    override def cloneType = (new IBufPtr).asInstanceOf[this.type]
}

class IBufferIO extends Bundle {
    val flush = Input(Bool())
    //val full = Output(Bool())
    val in = Flipped(DecoupledIO(new InstrPackage))
    val out = DecoupledIO(new InstrPackage)
}

class IBuffer(IBufSize: Int) extends Module with CircularQueuePtrHepler {
    val io = IO(new IBufferIO)

    class IBufferEntry extends Bundle {
        val inst = UInt(32.W)
        val pc = UInt(AddrBits.W)
        val prepcNext = UInt(AddrBits.W)
    }

    //val ibuf = Module(new SyncDataModuleTemplate(new IBufferEntry, IBufSize, DecodeWidth, PredictWidth))
    val ibuf = Mem(IBufSize, new IBufferEntry())

    // read write ptr
    val deq_ptr = RegInit(0.U.asTypeOf(new IBufPtr))
    val enq_ptr = RegInit(0.U.asTypeOf(new IBufPtr))

    val allowEnq = RegInit(true.B)

    val validEntries = distancePtr(enq_ptr, deq_ptr)

    val numEnq = Mux(io.in.fire(), 1.U, 0.U)
    val numTryDeq = Mux(validEntries  >= 1.U, 1.U, 0.U)
    val numDeq = Mux(io.out.fire(), numTryDeq, 0.U)

    val numAfterEnq = validEntries + numEnq
    val nextValidEntries = Mux(io.out.ready, numAfterEnq - numDeq, numAfterEnq)
    allowEnq := nextValidEntries <= (IBufSize - 1).U

    val inEntry = Wire(new IBufferEntry)
    inEntry.inst := io.in.bits.inst
    inEntry.pc := io.in.bits.pc
    inEntry.prepcNext := io.in.bits.prepcNext

    // write into buffer
    val bufferWen = io.in.fire() && !io.flush
    when(bufferWen) {
        ibuf.write(enq_ptr.value, inEntry)
        enq_ptr := enq_ptr + 1.U
    }

    val validDeq = validEntries >= 1.U

    val deqPtrNext = deq_ptr + numDeq
    deq_ptr := deqPtrNext
    val outEntry = ibuf.read(deq_ptr.value)//ibuf.io.rdata(i)

    // output data
    io.out.valid := validDeq && !io.flush
    io.out.bits.instValid := validDeq
    io.out.bits.pc := outEntry.pc
    io.out.bits.prepcNext := outEntry.prepcNext
    io.out.bits.inst := outEntry.inst

    io.in.ready := allowEnq

    when(io.flush) {
        allowEnq := true.B
        deq_ptr := 0.U.asTypeOf(new IBufPtr)
        enq_ptr := 0.U.asTypeOf(new IBufPtr)
    }

    when (reset.asBool()) {
        for (i <- 0 until IBufSize) {
            ibuf.write(i.U, 0.U.asTypeOf(new IBufferEntry))
        }
    }

}

